import bpy
import mathutils

import sys, os

import math

class Box3:
  CS_BOX_CORNER_xyz = 0
  CS_BOX_CORNER_xyZ = 1
  CS_BOX_CORNER_xYz = 2
  CS_BOX_CORNER_xYZ = 3
  CS_BOX_CORNER_Xyz = 4
  CS_BOX_CORNER_XyZ = 5
  CS_BOX_CORNER_XYz = 6
  CS_BOX_CORNER_XYZ = 7
  CS_BOX_CENTER3 = 8
  
  def __init__(self, *args):
    if len(args) == 0:
      self.minbox = mathutils.Vector([1000000000, 1000000000, 1000000000])
      self.maxbox = mathutils.Vector([-1000000000, -1000000000, -1000000000])
    elif len(args) == 1:
      self.minbox = mathutils.Vector([x for x in map(lambda *row: min(row), *args[0])])
      self.maxbox = mathutils.Vector([x for x in map(lambda *row: max(row), *args[0])])
    elif len(args) == 6:
      self.minbox = mathutils.Vector([args[0], args[1], args[2]])
      self.maxbox = mathutils.Vector([args[3], args[4], args[5]])
    
  def GetCenter(self):
    return (self.minbox+self.maxbox)/2
  
  def Min(self):
    return self.minbox
    
  def MinX(self):
    return self.minbox.x
    
  def MinY(self):
    return self.minbox.y
    
  def MinZ(self):
    return self.minbox.z
    
  def Max(self):
    return self.maxbox
    
  def MaxX(self):
    return self.maxbox.x
    
  def MaxY(self):
    return self.maxbox.y
    
  def MaxZ(self):
    return self.maxbox.z
    
  def __add__(self, other):
    return Box3 (
      min (self.minbox.x, other.minbox.x),
      min (self.minbox.y, other.minbox.y),
      min (self.minbox.z, other.minbox.z),
      max (self.maxbox.x, other.maxbox.x),
      max (self.maxbox.y, other.maxbox.y),
      max (self.maxbox.z, other.maxbox.z))
     
  def GetCorner(self, corner):
    MinX = self.MinX
    MinY = self.MinY
    MinZ = self.MinZ
    
    MaxX = self.MaxX
    MaxY = self.MaxY
    MaxZ = self.MaxZ
    if corner ==  Box3.CS_BOX_CORNER_xyz:
      return self.Min()
    elif corner ==  Box3.CS_BOX_CORNER_xyZ:
      return mathutils.Vector ([MinX (), MinY (), MaxZ ()])
    elif corner ==  Box3.CS_BOX_CORNER_xYz:
      return mathutils.Vector ([MinX (), MaxY (), MinZ ()])
    elif corner ==  Box3.CS_BOX_CORNER_xYZ:
      return mathutils.Vector ([MinX (), MaxY (), MaxZ ()])
    elif corner ==  Box3.CS_BOX_CORNER_Xyz:
      return mathutils.Vector ([MaxX (), MinY (), MinZ ()])
    elif corner ==  Box3.CS_BOX_CORNER_XyZ:
      return mathutils.Vector ([MaxX (), MinY (), MaxZ ()])
    elif corner ==  Box3.CS_BOX_CORNER_XYz:
      return mathutils.Vector ([MaxX (), MaxY (), MinZ ()])
    elif corner ==  Box3.CS_BOX_CORNER_XYZ:
      return self.Max()
    elif corner ==  Box3.CS_BOX_CENTER3:
      return self.GetCenter()
    else:
      return None

#====[ func ]=====================================================			
def ScaleCamera (camera, cameraob, mesh_box, txtw, txth):
  mesh_center = mesh_box.GetCenter()

  aspect = (txtw/txth)*2
  shift_x = camera.shift_x
  shift_y = camera.shift_y
  maxy = -10000000.0
  for i in range(8):
    corner = mesh_box.GetCorner(i) - mesh_center
    y = (corner[0] * aspect) / (1.0 - shift_x)
    if (y < 0):
      y = (corner[0] * aspect) / (float(txtw) - shift_x)
    y += corner[1]
    if (y > maxy):
      maxy = y

    y = (corner[2] * aspect) / (1.0 - shift_y)
    if (y < 0):
      y = (corner[2] * aspect) / (float(txth) - shift_y)
    y += corner[1]
    if (y > maxy):
      maxy = y;

  cam_pos = mesh_center;
  cam_pos.x += maxy
  
  #cam_pos.x += 2 #TODO: adding some extra distance, why?
  
  m = mathutils.Matrix.Translation(cam_pos)
  cameraob.matrix_world = m * mathutils.Euler([1.57,0,1.57]).to_matrix().resize4x4() 


def CreateCamera (scene):
  # Create a camera
  camdata = bpy.data.cameras.new('persp')
  cam = bpy.data.objects.new('Camera', object_data=camdata)
  #cam.data = camdata
  scene.objects.link(cam)
  scene.camera = cam
  return cam, camdata
    
def CreateLight (scene, cam):
  #Remove scene lights.
  lights = [light for ob in scene.objects if ob.type == 'Light']
  for light in lights:
    scene.objects.unlink(light)
  #Create some lights.
  l = bpy.data.lamps.new('Lamp')
  l.type = 'POINT'
  #l.energy = 5
  l.distance = 500
  ob = bpy.data.objects.new('Lamp', object_data=l)
  #ob.data = l
  scene.objects.link(ob)
  ob.location = cam.location
  return ob   
  
#====[ stuff ]====================================================
def CombinedBBox (meshes):
  bbox = Box3()
  for mesh in meshes:
    aa = []
    mesh_box = mesh.bound_box
    for i in range(8):
      corner = mesh_box[i]
      corner = mesh.matrix_world * mathutils.Vector(corner)
      aa.append(corner)
    bbox = bbox + Box3(aa)

  #Normalize the bbox in X/Y plane
  if bbox.minbox.x < bbox.minbox.y:
    bbox.minbox.y = bbox.minbox.x
  else:
    bbox.minbox.x = bbox.minbox.y
    
  if bbox.maxbox.x > bbox.maxbox.y:
    bbox.maxbox.y = bbox.maxbox.x
  else:
    bbox.maxbox.x = bbox.maxbox.y
  
          
  return bbox

def Rotate (meshes, rot, bbox):
  for mesh in meshes:
    bpy.data.objects[mesh.name].location -= bbox.GetCenter()
  for mesh in meshes: 
    #bpy.data.objects[mesh.name].matrix_local = rot * activeObject.matrix_local
    bpy.data.objects[mesh.name].matrix_world = rot * activeObject.matrix_world
 
     
#====[ stuff ]====================================================

typeName = os.environ["BlenderImageTranscoder-typeName"]

if typeName == 'image':
  imageName = os.environ["BlenderImageTranscoder-image"]
  path = os.environ["BlenderImageTranscoder-path"]
  
  if imageName in bpy.data.images:
    image = bpy.data.images[imageName]
    image.save_render(path)
    
elif typeName == 'mesh':
  objectName = os.environ["BlenderImageTranscoder-object"]
  path = os.environ["BlenderImageTranscoder-path"]
  sizex = int(os.environ["BlenderImageTranscoder-sizex"])
  sizey = int(os.environ["BlenderImageTranscoder-sizey"])
  
  cameraType = os.environ["BlenderImageTranscoder-cameraType"]
  cameraAngley = float(os.environ["BlenderImageTranscoder-cameraAngley"])
  
  sce = bpy.data.scenes[0]
  
  
  activeObjects = []
  bbox = None
  
  if typeName == 'mesh':
    activeObject = bpy.data.objects[objectName]
    activeObjects = [activeObject]
    #TODO add children recursively
    activeObjects.extend([child for child in activeObject.children if child.type == 'MESH'])
    #Compute BB
    bbox = CombinedBBox(activeObjects)
  elif typeName == 'group':
    pass
    '''
    rot = mathutils.RotationMatrix(angley, 4, 'Z')
    for o in activeObjects:
      if not o.parent:
        bpy.data.objects[o.name].matrix = rot * o.matrix
    '''
  print('BLAH', activeObjects)
  #Remove other objects
  for ob in sce.objects:
    if ob not in activeObjects:
      print("Removed object ", ob.name)
      sce.objects.unlink(ob)
      
  cam, camdata  = CreateCamera (sce)

  
    
  ScaleCamera (camdata, cam, bbox, sizex, sizey)
  light = CreateLight (sce, cam)
  
  
  #Transform cam
  if cameraAngley != 0.0:
    #vec = mathutils.Vector(0.0, 0.0, activeObject[0].location[2]*2)
    rot = mathutils.Matrix.Rotation(cameraAngley, 4, 'X')
    cam.matrix_local = cam.matrix_local  * rot
  
  camdata.type = cameraType
  if cameraType == 'ORTHO':
    camdata.ortho_scale = activeObjects[0].dimensions[2]*2
  
   
  #Assign a default material to atleast render UV textures
  material1 = bpy.data.materials.new('UV mat')
  material1.use_fake_user = True
  material1.type = 'SURFACE'
  material1.use_face_texture = True
  material1.use_face_texture_alpha = True
  #material1.shadeless = True
  material1.specular_intensity = 0
  material1.emit = 0.7
  
  material2 = bpy.data.materials.new('Default mat')
  material2.use_fake_user = True
  material2.type = 'SURFACE'
  material2.use_face_texture = False
  material2.use_face_texture_alpha = False
  material2.specular_intensity = 0
  material2.emit = 0.7
  
  for mesh in bpy.data.meshes:
    materials = [m for m in mesh.materials if m]
    if len(materials)==0: 
      print("ADDED Material")
      mesh.materials.append(material1)
      mesh.materials.append(material2)
      materials = [m for m in mesh.materials]
      m1i = materials.index(material1) 
      m2i = materials.index(material2)
      if mesh.uv_textures.active and mesh.uv_textures.active.data:
        print("I: mesh.active_uv_texture")
        for f, uv in zip(mesh.faces, mesh.uv_textures.active.data):
          #TODO: Ask for the size first else has_data returns false for no reason... 
          if uv.image and uv.image.size[0] and uv.image.has_data:
            #print("I: mesh.active_uv_texture: uv.image", m1i, materials[m1i].name)
            f.material_index = m1i
          else:
            #print("I: mesh.active_uv_texture: no data", m2i, materials[m2i].name, uv.image.size[0])
            f.material_index = m2i
      else:
        print("I: No UVs", m2i, materials[m2i].name)
        for f in mesh.faces:
          f.material_index = m2i
  
  #Remove world to get alpha 
  sce.world = None
  sce.render.file_format = "PNG"
  sce.render.color_mode = "RGBA"
  #sce.render.quality = 90
  sce.render.resolution_x = sizex
  sce.render.resolution_y = sizey
  sce.render.resolution_percentage = 100
  
  def Render(angley, path):
    rot = mathutils.Matrix.Rotation(angley, 4, 'Z')
    Rotate(activeObjects, rot, bbox)
    bpy.ops.render.render()
    #bpy.ops.render.opengl()
    img = bpy.data.images['Render Result']
    img.save_render(path, scene=sce)

  #Transform active
  if "BlenderImageTranscoder-anglesy" in os.environ: 
    anglesy = eval(os.environ["BlenderImageTranscoder-anglesy"])
    for angley in anglesy:
      p = path % angley 
      Render(angley, p)
  else:
    angley = float(os.environ["BlenderImageTranscoder-angley"])
    Render(angley, path)
